#include <proto_head.h>
#include <lsi.h>
#include <hash.h>
#include <xml_wrapper.h>
#include <sys/time.h>
#include <mm.h>
#include <crc32.h>
#include <util.h>
#include "proto.h"
#include "conn_helper.h"

#define MAX_CLIENT_COUNT 1024

typedef struct ServerCtx
{
	struct LSI* lsi;
	struct hash_t* conn_table;

	lsi_ip_t addr;
	lsi_ip_t conn_addr;
	lsi_id_t lsi_id;
	int32_t lsi_version;
}ServerCtx;

typedef struct Connection
{
	int32_t address;
}Connection;

int conn_hash(const void* data)
{
	struct Connection* conn = (struct Connection*)data;
	return conn->address;
}
int conn_cmp(const void* data1, const void* data2)
{
	struct Connection* conn1 = (struct Connection*)data1;
	struct Connection* conn2 = (struct Connection*)data2;
	return conn1->address - conn2->address;
}

void init(struct ServerCtx* ctx, const char* cfg_file)
{
	assert(ctx && cfg_file);

	mxml_node_t* tree = xml_load_file(cfg_file);
	assert(tree);
	mxml_node_t* cfg = xml_find_child_element(tree, tree, "config");
	assert(cfg);

	/* lsi id */
	mxml_node_t* cfg_lsi_id = xml_find_child_element(cfg, tree, "lsi_id");
	assert(cfg_lsi_id);
	ctx->lsi_id = atoi(xml_element_get_text(cfg_lsi_id));
	
	/* lsi ip */
	mxml_node_t* cfg_lsi_ip = xml_find_child_element(cfg, tree, "lsi_ip");
	assert(cfg_lsi_ip);
	ctx->conn_addr = lsi_addr_aton(xml_element_get_text(cfg_lsi_ip));

	/* lsi back-server ip */
	mxml_node_t* cfg_lsi_server_ip = xml_find_child_element(cfg, tree, "lsi_back_server_ip");
	assert(cfg_lsi_server_ip);
	ctx->addr = lsi_addr_aton(xml_element_get_text(cfg_lsi_server_ip));

	/* lsi version */
	mxml_node_t* cfg_lsi_version = xml_find_child_element(cfg, tree, "lsi_version");
	assert(cfg_lsi_version);
	ctx->lsi_version = atoi(xml_element_get_text(cfg_lsi_version));

	/* release xml */
	xml_release(tree);
	
	/* create lsi */
	ctx->lsi = lsi_init(ctx->lsi_id, ctx->addr, ctx->lsi_version);
	assert(ctx->lsi);

	/* create hash */
	ctx->conn_table = hash_init(conn_hash, conn_cmp, NULL, NULL, 1024);
	assert(ctx->conn_table);
}

void fini(struct ServerCtx* ctx)
{
	assert(ctx);

	lsi_release(ctx->lsi);
	ctx->lsi = 0;

	hash_release(ctx->conn_table);
	ctx->conn_table = 0;
}

void proc_connect(struct ServerCtx* ctx, struct server_head_t* head)
{
	struct Connection tmp;
	tmp.address = head->address;
	struct Connection* conn = hash_find(ctx->conn_table, &tmp);

	if(conn)
	{
		printf("connection address=%d reconnect...\n", head->address);
	}
	else if(hash_count(ctx->conn_table)  >= MAX_CLIENT_COUNT)
	{
		printf("connection exceeds max, can't accept any more\n");
		int ret = send_connect_res(ctx->lsi, ctx->conn_addr, head->address, -1);
		assert(0 == ret);
		return;
	}
	else
	{
		conn = MALLOC(sizeof(struct Connection));
		conn->address = head->address;
		int ret = hash_insert(ctx->conn_table, conn);
		assert(0 == ret);
		printf("connection address=%d connected\n", head->address);
	}

	int ret = send_connect_res(ctx->lsi, ctx->conn_addr, head->address, 0);
	assert(0 == ret);
}

void proc_disconnect(struct ServerCtx* ctx, struct server_head_t* head)
{
	struct Connection temp;
	temp.address = head->address;
	printf("connection address=%d disconnect\n", head->address);

	struct Connection* conn = hash_find(ctx->conn_table, &temp);
	if(conn)
	{
		hash_remove(ctx->conn_table, &temp);
		FREE(conn);
	}
}

void proc_pkg(struct ServerCtx* ctx, struct server_head_t* head, char* pkgbuf)
{
	struct Connection temp;
	temp.address = head->address;
	//printf("connection address=%d pkg\n", head->address);
	struct Connection* conn = hash_find(ctx->conn_table, &temp);
	assert(conn && conn->address == head->address);

	int32_t crc = crc32(pkgbuf, head->pkg_len);
	assert(crc == head->pkg_crc);

	struct C2S_PKG req;
	struct C2S_PKG res;
	int ret = deserialize_pkg(&req, pkgbuf, head->pkg_len);
	assert(0 == ret && req.cmd == ID_C2S_REQ_CALC);
	
	res.arg_1 = req.arg_1;
	res.arg_2 = req.arg_2;
	res.calc_op = req.calc_op;
	res.cmd = ID_S2C_RES_CALC;
	memcpy(res.buffer, req.buffer, sizeof(req.buffer));
	switch(req.calc_op)
	{
		case E_CALC_ADD:
			res.arg_3 = req.arg_1 + req.arg_2;
			//printf("%d + %d = %d\n", req.arg_1, req.arg_2, res.arg_3);
			break;
		case E_CALC_DIV:
			if(req.arg_2 == 0)
			{
				printf("%d / %d, invalid disconnect\n", req.arg_1, req.arg_2);
				send_disconnect_ntf(ctx->lsi, ctx->conn_addr, head->address, -1);
				return;
			}
			res.arg_3 = req.arg_1 / req.arg_2;
			//printf("%d / %d = %d\n", req.arg_1, req.arg_2, res.arg_3);
			break;
		case E_CALC_MUL:
			res.arg_3 = req.arg_1 * req.arg_2;
			//printf("%d * %d = %d\n", req.arg_1, req.arg_2, res.arg_3);
			break;
		case E_CALC_SUB:
			res.arg_3 = req.arg_1 - req.arg_2;
			//printf("%d - %d = %d\n", req.arg_1, req.arg_2, res.arg_3);
			break;
		default:
			printf("pkg op=%d fail\n", req.calc_op);
			assert(0);
			break;
	}

	/* send back */ 
	ret = send_pkg_ntf(ctx->lsi, ctx->conn_addr, head->address, serialize_pkg, &res);
	assert(0 == ret);
}

int main(int argc, char** argv)
{
	if(argc != 2)
	{
		printf("usage: ./server config_file\n");
		return -1;
	}
	
	struct ServerCtx* ctx = (struct ServerCtx*)MALLOC(sizeof(struct ServerCtx));
	init(ctx, argv[1]);

	char recvbuf[1024 * 64];
	while(1)
	{
		size_t recvbuf_len = sizeof(recvbuf);
		int ret = lsi_recv(ctx->lsi, ctx->conn_addr, recvbuf, &recvbuf_len);
		if(0 == ret)
		{
			assert(recvbuf_len >= sizeof(struct server_head_t));
			struct server_head_t head;
			deserialize_server_head(&head, recvbuf, sizeof(struct server_head_t));

			struct timeval tv;
			char cur_timestamp[128];
			char pkg_timestamp[128];
			tv.tv_usec = (uint32_t)(head.timestamp & 0xFFFFFFFF);
			tv.tv_sec = (uint32_t)(head.timestamp >>  32);
			get_timestamp(&tv, pkg_timestamp);
			gettimeofday(&tv, NULL);
			get_timestamp(&tv, cur_timestamp);
			//printf("%s, receive pkg cmd=%d timestamp %s\n", cur_timestamp, head.cmd, pkg_timestamp);
			
			switch(head.cmd)
			{
				case ID_CONN2S_REQ_CONNECT:
					assert(recvbuf_len == sizeof(head));
					proc_connect(ctx, &head);
					break;

				case ID_CONN2S_NTF_DISCONN:
					assert(recvbuf_len == sizeof(head));
					proc_disconnect(ctx, &head);
					break;

				case ID_CONN2S_NTF_PKG:
					assert(recvbuf_len == sizeof(head) + head.pkg_len);
					proc_pkg(ctx, &head, recvbuf + sizeof(head));
					break;

				default:
					printf("unrecognized cmd=%d\n", head.cmd);
					assert(0);
			}
		}
		else
		{
			usleep(10);
		}
	}

	fini(ctx);	
	FREE(ctx);
}

